﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Threading;
using Microsoft.WindowsAPICodePack.DirectX.Direct2D1;
using Microsoft.WindowsAPICodePack.DirectX.DirectWrite;
using SpectrumAnalyser.Common.Extensions;
using SpectrumAnalyser.Controls.Wpf.Converters;
using SpectrumAnalyser.Controls.Wpf.Helpers;
using SpectrumAnalyser.Controls.Wpf.ViewModels;
using SpectrumAnalyser.CustomControls.Wpf.Direct2D;
using SpectrumAnalyser.ModelObjects;
using Brush = System.Windows.Media.Brush;
using Direct2DControl = Direct2D.Direct2DControl;
using Path = System.Windows.Shapes.Path;
using PathGeometry = System.Windows.Media.PathGeometry;
using SolidColorBrush = System.Windows.Media.SolidColorBrush;

namespace SpectrumAnalyser.CustomControls.Wpf
{
    /// <summary>
    /// Interaction logic for UserControl1.xaml
    /// </summary>
    public partial class HorizontalLineChart : UserControl
    {
        #region Privates

        private int _xAxisTicks = 10;
        private int _yAxisTicks = 10;
        private const double MarginTop = 150;
        private double _scaleX = 2;
        private double _scaleY = 1;
        private UIElementCollection _uiElementCollection;

        private Color _blueColor = Color.FromRgb(28, 151, 234);
        private Color _greenColor = Color.FromRgb(0, 159, 60);
        private Color _redColor = Color.FromRgb(255, 56, 62);

        private readonly TimeSpan _scrollInterval = TimeSpan.FromMilliseconds(500);
        private readonly DispatcherTimer _scrollTimer;

        #endregion

        private readonly Direct2DControl _directGraphDirect2DControl;
        private readonly Direct2DControl _directPeaks2DControl;
        private Graph2DScene _myScene;
        private Peaks2DScene _peaks2DScene;

        public HorizontalLineChart()
        {
            InitializeComponent();

            ChartCanvasScroll.SizeChanged += (sender, args) =>
                                                 {
                                                     UpdateSize(args.NewSize);
                                                     Render();
                                                 };

            Brush = new SolidColorBrush(Color.FromRgb(28, 151, 234));
            SecondBrush = new SolidColorBrush(Color.FromRgb(0, 159, 60));
            PeaksBrush = new SolidColorBrush(Color.FromRgb(255, 56, 62));
            DataContext = this;

            _directGraphDirect2DControl = new Direct2DControl {Width = 1200, Height = 600};
            _directPeaks2DControl = new Direct2DControl {Width = 1200, Height = 600};

            Unloaded += (sender, args) =>
                            {
                                if (!ReferenceEquals(_myScene, null))
                                {
                                    _myScene.Dispose();
                                }
                                if (!ReferenceEquals(_peaks2DScene, null))
                                {
                                    _peaks2DScene.Dispose();
                                }
                            };

            this._scrollTimer = new DispatcherTimer();
            this._scrollTimer.Interval = this._scrollInterval;
            this._scrollTimer.Tick += this.ResizeTimerTick;
        }

        #region Public Properties

        public int XAxisTicks
        {
            get { return _xAxisTicks; }
            set { _xAxisTicks = value; }
        }
        public int YAxisTicks
        {
            get { return _yAxisTicks; }
            set { _yAxisTicks = value; }
        }

        public double ScaleX
        {
            get { return _scaleX; }
            set
            {
                //after scroll is shown we need to hide it manualy. it does not hides automatically if scroll is not needed.
                ChartCanvasScroll.HorizontalScrollBarVisibility = value == 1 ? ScrollBarVisibility.Hidden : ScrollBarVisibility.Auto;

                if (value >= 1)
                {
                    _scaleX = value;
                    UpdateSize(ChartCanvasScroll.RenderSize);
                    Render();
                }
            }
        }

        public double ScaleY
        {
            get { return _scaleY; }
            set
            {
                //after scroll is shown we need to hide it manualy. it does not hides automatically if scroll is not needed.
                ChartCanvasScroll.HorizontalScrollBarVisibility = value == 1 ? ScrollBarVisibility.Hidden : ScrollBarVisibility.Auto;

                if (value >= 1)
                {
                    _scaleY = value;
                    UpdateSize(ChartCanvasScroll.RenderSize);
                    Render();
                }
            }
        }

        public static readonly DependencyProperty SourceProperty =
            DependencyProperty.Register("Source", typeof(ObservableCollection<SpectrumDataPointViewModel>), typeof(HorizontalLineChart), new PropertyMetadata(default(ObservableCollection<SpectrumDataPointViewModel>)));

        public ObservableCollection<SpectrumDataPointViewModel> Source
        {
            get { return (ObservableCollection<SpectrumDataPointViewModel>)GetValue(SourceProperty); }
            set
            {
                SetValue(SourceProperty, value);
                //Render();
            }
        }

        public static readonly DependencyProperty WaveletSourceProperty =
            DependencyProperty.Register("WaveletSource", typeof(ObservableCollection<SpectrumDataPointViewModel>), typeof(HorizontalLineChart), new PropertyMetadata(default(ObservableCollection<SpectrumDataPointViewModel>)));

        public ObservableCollection<SpectrumDataPointViewModel> WaveletSource
        {
            get { return (ObservableCollection<SpectrumDataPointViewModel>) GetValue(WaveletSourceProperty); }
            set
            {
                SetValue(WaveletSourceProperty, value);
                //Render();
            }
        }

        public static readonly DependencyProperty PeaksProperty =
            DependencyProperty.Register("Peaks", typeof (ObservableCollection<Peak>), typeof (HorizontalLineChart), new PropertyMetadata(default(ObservableCollection<Peak>)));

        public ObservableCollection<Peak> Peaks
        {
            get { return (ObservableCollection<Peak>) GetValue(PeaksProperty); }
            set { SetValue(PeaksProperty, value); }
        }

        public static readonly DependencyProperty BrushProperty =
            DependencyProperty.Register("Brush", typeof (Brush), typeof (HorizontalLineChart), new PropertyMetadata(default(Brush)));

        public Brush Brush
        {
            get { return (Brush) GetValue(BrushProperty); }
            set { SetValue(BrushProperty, value); }
        }

        public static readonly DependencyProperty SecondBrushProperty =
            DependencyProperty.Register("SecondBrush", typeof (Brush), typeof (HorizontalLineChart), new PropertyMetadata(default(Brush)));

        public Brush SecondBrush
        {
            get { return (Brush) GetValue(SecondBrushProperty); }
            set { SetValue(SecondBrushProperty, value); }
        }

        public static readonly DependencyProperty PeaksBrushProperty =
            DependencyProperty.Register("PeaksBrush", typeof (Brush), typeof (HorizontalLineChart), new PropertyMetadata(default(Brush)));

        public Brush PeaksBrush
        {
            get { return (Brush) GetValue(PeaksBrushProperty); }
            set { SetValue(PeaksBrushProperty, value); }
        }

        public static readonly DependencyProperty IsPeaksAddingProperty =
            DependencyProperty.Register("IsPeaksAdding", typeof(bool), typeof(HorizontalLineChart), new PropertyMetadata(default(bool)));

        public bool IsPeaksAdding
        {
            get { return (bool)GetValue(IsPeaksAddingProperty); }
            set { SetValue(IsPeaksAddingProperty, value); }
        }
        #endregion

        #region Rendering
        
        private void RenderXAxis(RenderTarget renderTarget, IDictionary<Color, Microsoft.WindowsAPICodePack.DirectX.Direct2D1.SolidColorBrush> brushes, TextFormat textFormat, StrokeStyle strokeStyle)
        {
            if ((ReferenceEquals(_viewPoints, null) || !_viewPoints.Any()))
            {
                return;
            }

            var xStep = ChartCanvasScroll.ActualWidth / _viewPoints.Count;

            var step = (ChartCanvas.ActualWidth - 50) / Source.Count;
            var skip = (int)(ChartCanvasScroll.HorizontalOffset / step);

            for (int i = 0; i < XAxisTicks; i++)
            {
                var tickCount = i*_viewPoints.Count/XAxisTicks;

                renderTarget.DrawText(Math.Round(Source[tickCount + skip].SpectrumPoint.X).ToString(),
                                      textFormat,
                                      new RectF((float) (xStep*tickCount + 50),
                                                (float) ChartCanvasScroll.ActualHeight - 50,
                                                (float) (xStep*tickCount + 50) + 30,
                                                (float) ChartCanvasScroll.ActualHeight - 50),
                                      brushes[Colors.DarkGray]);

                if (i == 0)
                {
                    renderTarget.DrawLine(
                        new Point2F((float) (xStep*tickCount + 50), (float) ChartCanvasScroll.ActualHeight - 50),
                        new Point2F((float) (xStep*tickCount + 50), 0),
                        brushes[Colors.DarkGray], 1);
                }
                else
                {
                    renderTarget.DrawLine(
                        new Point2F((float)(xStep * tickCount + 50), (float)ChartCanvasScroll.ActualHeight - 50),
                        new Point2F((float)(xStep * tickCount + 50), 0),
                        brushes[Colors.DarkGray], 1, strokeStyle);
                }
            }
        }

        private void RenderYAxis(RenderTarget renderTarget, IDictionary<Color, Microsoft.WindowsAPICodePack.DirectX.Direct2D1.SolidColorBrush> brushes, TextFormat textFormat, StrokeStyle strokeStyle)
        {
            if ((ReferenceEquals(Source, null) || !Source.Any()))
            {
                return;
            }

            var availableHeight = ChartCanvas.ActualHeight - 50; //20 - heght of horizontalscrollbar.
            var actualHeight = ChartCanvasScroll.ActualHeight - 50;
            var maxYValue = Source.ToList().MaxBy(x => x.SpectrumPoint.Y);
            var yPerPixel = (maxYValue.SpectrumPoint.Y)/(availableHeight - MarginTop + 50);
            var yStep = (int)(actualHeight / YAxisTicks);
            for (double i = actualHeight; i > 0; i -= yStep)
            {
                renderTarget.DrawText(Math.Round((availableHeight - i - ChartCanvasScroll.VerticalOffset)*yPerPixel).ToString(),
                                      textFormat,
                                      new RectF(50, (float) i, 30, (float) i),
                                      brushes[Colors.DarkGray]);
                if (i == actualHeight)
                {
                    renderTarget.DrawLine(
                        new Point2F(50, (float) (i)),
                        new Point2F((float) ChartCanvasScroll.ActualWidth, (float) (i)),
                        brushes[Colors.DarkGray], 1);
                }else
                {
                    renderTarget.DrawLine(
                        new Point2F(50, (float)(i)),
                        new Point2F((float)ChartCanvasScroll.ActualWidth, (float)(i)),
                        brushes[Colors.DarkGray], 1, strokeStyle);
                }
            }
        }

        private PathSegmentCollection _sourceSegmentCollection;
        private void RenderInner()
        {
            if ((ReferenceEquals(Source, null) || !Source.Any()) || (ReferenceEquals(WaveletSource, null) || !WaveletSource.Any()) || (ReferenceEquals(Peaks, null) || !Peaks.Any()))
            {
                return;
            }
            ChartCanvas.Children.Clear();

            var availableHeight = ChartCanvas.ActualHeight;
            var step =  (ChartCanvas.ActualWidth - YAxis.ActualWidth)/Source.Count;
            var maxYValue = Source.ToList().MaxBy(x => x.SpectrumPoint.Y);
            var yScale = (availableHeight - MarginTop)/(maxYValue.SpectrumPoint.Y);
            var x2 = step;

            var sourcePointCollection = new List<SpectrumDataPointViewModel>();
            _sourceSegmentCollection = new PathSegmentCollection();
            var sourceChartCanvas = new Canvas
            {
                Background = Brushes.Transparent,
            };
            var sourceChart = new Path
            {
                Stroke = Brush,
                Data = CreatePathFigureCollection(new Point(x2, 
                    (availableHeight - Source[0].SpectrumPoint.Y * yScale)), 
                    _sourceSegmentCollection)
            };
            sourceChartCanvas.Children.Add(sourceChart);
            sourceChartCanvas.SetBinding(Canvas.VisibilityProperty,
                                         new Binding("IsChecked")
                                             {
                                                 Source = InputSpectrumVisibility,
                                                 Converter = new BooleanToVisibilityConverter()
                                             });
            ChartCanvas.Children.Add(sourceChartCanvas);
            Canvas.SetZIndex(sourceChartCanvas, 11);

            var smothedSegmentCollection = new PathSegmentCollection();
            var smoothedChart = new Path
                {
                    Stroke = SecondBrush,
                    Data = CreatePathFigureCollection(new Point((double)x2, 
                        (double)(availableHeight - WaveletSource[0].SpectrumPoint.Y * yScale)), 
                        smothedSegmentCollection)
                };
            smoothedChart.SetBinding(Path.VisibilityProperty,
                                   new Binding("IsChecked")
                                   {
                                       Source = SmoothedSpectrumVisibility,
                                       Converter = new BooleanToVisibilityConverter()
                                   });
            ChartCanvas.Children.Add(smoothedChart);
            Canvas.SetZIndex(smoothedChart, 11);

            var loaclPeaks = new ObservableCollection<Peak>(Peaks.ToList());

            var resultPeaksPoints = loaclPeaks.Select(peak => peak.ViewPoints = (from point in peak.Points
                                                          let index = Source.IndexOf(Source.FirstOrDefault(x => x.SpectrumPoint.X == point.X)) + 1
                                                          select new SpectrumPoint(step * index, availableHeight - point.Y * yScale)).ToList()).ToList();

            var counter = 0;
            foreach (var lineDataPoint in Source)
            {
                //Source Points.
                x2 += step;
                lineDataPoint.ViewPoint = new Point((double)x2, (double)(availableHeight - lineDataPoint.SpectrumPoint.Y * yScale));
                sourcePointCollection.Add(lineDataPoint);
                _sourceSegmentCollection.Add(new LineSegment(lineDataPoint.ViewPoint, true));

                //Match Smoothed Point
                if (WaveletSource.Any(x => x.SpectrumPoint.X == lineDataPoint.SpectrumPoint.X))
                {
                    var matchedSmoothedPoint = WaveletSource.FirstOrDefault(x => x.SpectrumPoint.X == lineDataPoint.SpectrumPoint.X);
                    smothedSegmentCollection.Add(new LineSegment(new Point((double)x2, (double)(availableHeight - matchedSmoothedPoint.SpectrumPoint.Y * yScale)), true));
                }

                //update ui.
                if (counter % 500 == 0)
                {
                    UIHelper.ForceUIToUpdate();
                }
                counter++;
            }

            foreach (var peak in loaclPeaks)
            {
                RenderPeak(peak);
            }

            _uiElementCollection = sourceChartCanvas.Children;
            RenderSourceDataPoints(sourcePointCollection);
        }

        private void RenderPeak(Peak peak)
        {
            if (!peak.ViewPoints.Any())
            {
                return;
            }
            var peakPath = new Path
                               {
                                   Tag = "Peak",
                                   DataContext = peak,
                                   Stroke = PeaksBrush,
                                   Data = CreatePathFigureCollection(new Point((double)peak.ViewPoints[0].X, (double)peak.ViewPoints[0].Y),
                                                                  new PathSegmentCollection(GetBezierSegments(peak.ViewPoints)))
                               };
            peakPath.SetBinding(VisibilityProperty, new Binding("IsVisible") { Mode = BindingMode.TwoWay, Converter = new BoolToVisibilityConverter() });

            ChartCanvas.Children.Add(peakPath);
            Panel.SetZIndex(peakPath, 12);

            if (!ReferenceEquals(peak.MatchedChemicalElements, null))
            {
                foreach (var matchedChemicalElement in peak.MatchedChemicalElements)
                {
                    var label = new Label {Content = matchedChemicalElement.Name, DataContext = matchedChemicalElement};
                    label.SetBinding(VisibilityProperty, new Binding("IsVisible") {Mode = BindingMode.TwoWay, Converter = new BoolToVisibilityConverter()});

                    ChartCanvas.Children.Add(label);
                    Canvas.SetLeft(label, (double)peak.ViewPoints.MaxBy(x => x.Y).X);
                    Canvas.SetTop(label, (double)peak.ViewPoints.MinBy(x => x.Y).Y - 20);
                    Panel.SetZIndex(label, 12);
                }
            }
        }

        private int _dataPointClickCount;
        private SpectrumDataPointViewModel _fstPoint;
        private SpectrumDataPointViewModel _scdPoint;
        private SpectrumDataPointViewModel _tmpComparePoint;
        private void RenderSourceDataPoints(List<SpectrumDataPointViewModel> sourcePointCollection)
        {
            for (int i = 0; i < sourcePointCollection.Count - 1; i++)
            {
                var dataPoint = sourcePointCollection[i].DataPointGeometry;
                dataPoint.MouseEnter += (o, e) =>
                {
                    if (IsPeaksAdding && o is Shape)
                    {
                        (o as Shape).Fill = Brush;
                    }
                };
                dataPoint.MouseLeave += (o, e) =>
                {
                    if (o is Shape)
                    {
                        (o as Shape).Fill = Brushes.Transparent;
                    }
                };
                dataPoint.MouseLeftButtonDown += DataPointClick;
                
                dataPoint.DataContext = sourcePointCollection[i];
                dataPoint.Width = 10;
                dataPoint.Height = 10;
                dataPoint.Fill = Brushes.Transparent;
                Canvas.SetLeft(dataPoint, sourcePointCollection[i].ViewPoint.X - 5);
                Canvas.SetTop(dataPoint, sourcePointCollection[i].ViewPoint.Y - 5);
                _uiElementCollection.Add(dataPoint);
            }
        }

        private void DataPointClick(object sender, RoutedEventArgs e)
        {
            if (!(sender is Shape))
                return;
            var datapoint = sender as Shape;
            if (!(datapoint.DataContext is SpectrumDataPointViewModel))
                return;

            if (!IsPeaksAdding)
            {
                return;
            }

            _dataPointClickCount++;
            if (_dataPointClickCount%2 == 0)
            {
                _scdPoint = datapoint.DataContext as SpectrumDataPointViewModel;
                if (_fstPoint.ViewPoint.X > _scdPoint.ViewPoint.X)
                {
                    _tmpComparePoint = _fstPoint;
                    _fstPoint = _scdPoint;
                    _scdPoint = _tmpComparePoint;
                }

                var points = Source.Where(
                    x =>
                    x.SpectrumPoint.X > _fstPoint.SpectrumPoint.X &&
                    x.SpectrumPoint.X < _scdPoint.SpectrumPoint.X).Select(x => x.SpectrumPoint).ToList();
                var viewPoints =
                    _sourceSegmentCollection.Where(x => (x as LineSegment).Point.X > _fstPoint.ViewPoint.X &&
                                                        (x as LineSegment).Point.X < _scdPoint.ViewPoint.X).
                        Select(x => new SpectrumPoint((x as LineSegment).Point.X, (x as LineSegment).Point.Y)).ToList();
                var peak = new Peak
                               {
                                   ViewPoints = viewPoints,
                                   Point1 = _fstPoint.SpectrumPoint,
                                   Point3 = _scdPoint.SpectrumPoint,
                                   Points = points,
                                   Point2 = points.MaxBy(x => x.Y)
                               };
                Peaks.Add(peak);
                _fstPoint.DataPointGeometry.Fill = Brush;
                RenderPeak(peak);
            }
            else
            {
                _fstPoint = datapoint.DataContext as SpectrumDataPointViewModel;
                _fstPoint.DataPointGeometry.Fill = PeaksBrush;
            }
        }

        private void SnowHidePeaks(object sender, RoutedEventArgs e)
        {
            if (!(sender is CheckBox))
            {
                return;
            }

            var show = (sender as CheckBox).IsChecked;
            if (!show.HasValue)
            {
                return;
            }

            var peakPaths = ChartCanvas.Children.OfType<Path>().Where(x => ReferenceEquals(x.Tag, "Peak")).ToList();
            foreach (var peakPath in peakPaths)
            {
                peakPath.Stroke = show.Value ? PeaksBrush : Brushes.Transparent;
            }
        }

        private IEnumerable<PathSegment> GetBezierSegments(IEnumerable<SpectrumPoint> points )
        {
            var pathSegmentCollection = new PathSegmentCollection();

            foreach (SpectrumPoint point in points)
            {
                pathSegmentCollection.Add(new LineSegment
                    {
                        Point = new Point((double)point.X, (double)point.Y),
                        IsSmoothJoin = true
                    });
            }

            return pathSegmentCollection;
        }

        private PathGeometry CreatePathFigureCollection(Point startPoint, PathSegmentCollection collection)
        {
            return new PathGeometry
            {
                Figures = new PathFigureCollection
                                              {
                                                  new PathFigure
                                                      {
                                                          StartPoint = startPoint,
                                                          Segments = collection
                                                      }
                                              }
            };
        }

        public void Render()
        {
            if (Source == null)
            {
                return;
            }
            var sourcePoints = CalculatePoints(Source);
            if (_peaks2DScene != null)
            {
                _peaks2DScene.Peaks = CalculatePeakPoints(Peaks.ToList(),
                                                     sourcePoints.ToList());
                _peaks2DScene.Render();
            }
            else
            {
                _myScene = new Graph2DScene
                               {
                                   IsAnimating = false,
                                   Source = new Dictionary<Color, IList<SpectrumDataPointViewModel>>
                                                {
                                                    {
                                                        _blueColor,
                                                        sourcePoints
                                                    },
                                                    {
                                                        _greenColor,
                                                        CalculatePoints(WaveletSource)
                                                    }
                                                },
                                   Colors = new List<Color>
                                                {
                                                    _blueColor,
                                                    _greenColor,
                                                    Colors.DarkGray
                                                }
                               };

                _myScene.OnRenderEvent += RenderXAxis;
                _myScene.OnRenderEvent += RenderYAxis;

                _peaks2DScene = new Peaks2DScene
                                    {
                                        IsAnimating = false,
                                        Peaks = CalculatePeakPoints(Peaks.ToList(), sourcePoints.ToList())
                                    };
                try
                {
                    _directGraphDirect2DControl.Scene = _myScene;
                    _directPeaks2DControl.Scene = _peaks2DScene;
                }
                catch (InsufficientMemoryException)
                {
                    try
                    {
                        _directGraphDirect2DControl.Scene = _myScene;
                        _directPeaks2DControl.Scene = _peaks2DScene;
                    }
                    catch (InsufficientMemoryException)
                    {
                        _directGraphDirect2DControl.Scene = _myScene;
                        _directPeaks2DControl.Scene = _peaks2DScene;
                    }
                }

                ChartCanvas.Children.Add(_directGraphDirect2DControl);
                ChartCanvas.Children.Add(_directPeaks2DControl);
            }
        }

        private BitmapCache _bitmapCache = new BitmapCache();
        private void Render(ObservableCollection<SpectrumDataPointViewModel> source, Brush brush,
                            CheckBox visibilitySource)
        {
            if (ReferenceEquals(source, null))
            {
                return;
            }
            _bitmapCache.RenderAtScale = ScaleX;
            _bitmapCache.SnapsToDevicePixels = true;
            _bitmapCache.EnableClearType = true;

            var availableHeight =  ChartCanvas.ActualHeight;
            var step =  (ChartCanvas.ActualWidth - YAxis.ActualWidth)/source.Count;
            var maxYValue = source.ToList().MaxBy(x => x.SpectrumPoint.Y);
            var yScale = (availableHeight - MarginTop)/(maxYValue.SpectrumPoint.Y);
            var x2 = step;
            var myImage = new Image();
            myImage.SetBinding(Image.VisibilityProperty,
                               new Binding("IsChecked")
                                   {
                                       Source = visibilitySource,
                                       Converter = new BooleanToVisibilityConverter()
                                   });
            var pen = new Pen(brush, 1);

            var drawingVisual = new DrawingVisual();
            using (DrawingContext drawingContext = drawingVisual.RenderOpen())
            {
                for (int i = 1; i < source.Count; i++)
                {
                    drawingContext.DrawLine(pen,
                                            new Point( x2,  (source[i - 1].SpectrumPoint.Y*yScale)),
                                            new Point( (x2 + step),  (source[i].SpectrumPoint.Y*yScale)));
                    x2 += step;
                }

                // Persist the drawing content.
                drawingContext.Close();

                var bmp = new RenderTargetBitmap((int) ChartCanvas.ActualWidth,
                                                 (int) ChartCanvas.ActualHeight, 96, 96,
                                                 PixelFormats.Pbgra32);

                bmp.Render(drawingVisual);
                myImage.Source = bmp;
                ChartCanvas.Children.Add(myImage);
            }
        }

        private IList<SpectrumDataPointViewModel> _viewPoints;
        private IList<SpectrumDataPointViewModel> CalculatePoints(ObservableCollection<SpectrumDataPointViewModel> source)
        {
            
            if (ReferenceEquals(source, null))
            {
                return null;
            }

            var availableHeight = ChartCanvas.ActualHeight; //20 - heght of horizontalscrollbar.
            var actualHeight = ChartCanvasScroll.ActualHeight - 50;
            var step = (ChartCanvas.ActualWidth - 50) / source.Count;
            var maxYValue = source.ToList().MaxBy(x => x.SpectrumPoint.Y);
            var yScale = (availableHeight - MarginTop)/(maxYValue.SpectrumPoint.Y);
            var x2 = step;

            var spectrumPoints = new List<SpectrumDataPointViewModel>();
            foreach (SpectrumDataPointViewModel t in source)
            {
                spectrumPoints.Add(new SpectrumDataPointViewModel(x2, (availableHeight - t.SpectrumPoint.Y*yScale))
                    {
                        SpectrumPoint = t.SpectrumPoint
                    });
                x2 += step;
            }

            var skip = (int)(ChartCanvasScroll.HorizontalOffset/step);
            var take = (int) ((ChartCanvasScroll.ActualWidth - 50)/step);

            var viewPoints = spectrumPoints.Skip(skip).Take(take).ToList();
            var firstPointOffset = viewPoints.FirstOrDefault().ViewPoint.X;
            foreach (var spectrumPoint in viewPoints)
            {
                spectrumPoint.ViewPoint = new Point(spectrumPoint.ViewPoint.X - firstPointOffset + 50,
                                                    spectrumPoint.ViewPoint.Y - ChartCanvasScroll.VerticalOffset - 50);
            }


            _viewPoints = viewPoints;
            return viewPoints;
        }

        private IList<SpectrumDataPointViewModel> CalculatePeakPoints(IList<Peak> source, IList<SpectrumDataPointViewModel> sourceBase)
        {
            if (ReferenceEquals(source, null))
            {
                return null;
            }

            var availableHeight = ChartCanvas.ActualHeight;
            var step = (ChartCanvas.ActualWidth - YAxis.ActualWidth) / sourceBase.Count;
            var maxYValue = sourceBase.ToList().MaxBy(x => x.SpectrumPoint.Y);
            var yScale = (availableHeight - MarginTop) / (maxYValue.SpectrumPoint.Y);
            var x2 = step;

            var spectrumPoints = new List<SpectrumDataPointViewModel>();
            var selectedPeaks = source.Where(x => x.IsVisible).ToList();
            foreach (var basePoint in sourceBase)
            {
                var sourcePoint = selectedPeaks.FirstOrDefault(x => x.Point2.X == basePoint.SpectrumPoint.X);
                if (sourcePoint != null)
                {
                    spectrumPoints.Add(new SpectrumDataPointViewModel(basePoint.ViewPoint.X, basePoint.ViewPoint.Y)
                                           {
                                               SpectrumPoint = basePoint.SpectrumPoint,
                                               LabelText = sourcePoint.Info
                                           });
                }
                x2 += step;
            }



            return spectrumPoints;
        }

        public void Export(Stream stream)
        {
            var rtb = new RenderTargetBitmap(
                (int)MainCanvas.Width, //width 
                (int)MainCanvas.Height, //height 
                96, //dpi x 
                96, //dpi y 
                PixelFormats.Indexed8 // pixelformat 
                );
            rtb.Render(MainCanvas);

            SaveRTBAsPNG(rtb, stream);
        }

        private static void SaveRTBAsPNG(RenderTargetBitmap bmp, Stream stream)
        {
            var enc = new PngBitmapEncoder();
            enc.Frames.Add(BitmapFrame.Create(bmp));

            enc.Save(stream);
            stream.Close();
        } 

        #endregion

        #region Events

        public delegate void ScrollChangedEventHandler(ScrollChangedEventArgs e);
        public event ScrollChangedEventHandler ScrollChanged;
        #endregion

        #region Scrolling, Zoom and Minimap

        public void ScrollTo(double hOffset, double vOffset)
        {
            ChartCanvasScroll.ScrollToHorizontalOffset(hOffset);
            ChartCanvasScroll.ScrollToVerticalOffset(vOffset);
        }

        private void UpdateSize(Size size)
        {
            if (Math.Abs(Root.ActualHeight - 0.0) < 0.1 || Math.Abs(Root.ActualWidth - 0.0) < 0.1)
            {
                return;
            }

            MainCanvas.Height = size.Height * ScaleY;
            MainCanvas.Width = size.Width * ScaleX;

            ChartCanvas.Height = size.Height * ScaleY;
            ChartCanvas.Width = size.Width * ScaleX;

            _directGraphDirect2DControl.Width = ChartCanvasScroll.ActualWidth;
            _directGraphDirect2DControl.Height = ChartCanvasScroll.ActualHeight;

            _directPeaks2DControl.Width = ChartCanvasScroll.ActualWidth;
            _directPeaks2DControl.Height = ChartCanvasScroll.ActualHeight;

            XAxis.Width = ChartCanvas.Width;
            YAxis.Height = ChartCanvas.Height;

            UpdateLayout();
        }

        private Point _mouseDragStartSpectrumPoint;

        private void ChartCanvasPreviewMouseLeftButtonDown1(object sender, MouseButtonEventArgs e)
        {
            ZoomArea.Width = 0;

            _mouseDragStartSpectrumPoint = e.GetPosition(ChartCanvasScroll);

            ChartCanvas.CaptureMouse();

            base.OnPreviewMouseDown(e);
        }

        private bool _isVertical;
        private bool _isHorizontal;
        private bool _onceTimeIndicator = true;

        private void ChartCanvasPreviewMouseMove1(object sender, MouseEventArgs e)
        {
            if (ChartCanvas.IsMouseCaptured)
            {
                // Get the new mouse position. 
                Point mouseDragCurrentSpectrumPoint = e.GetPosition(ChartCanvasScroll);

                // Determine the new amount to scroll.
                var deltaX = _mouseDragStartSpectrumPoint.X - mouseDragCurrentSpectrumPoint.X;
                var deltaY = _mouseDragStartSpectrumPoint.Y - mouseDragCurrentSpectrumPoint.Y;

                if (Math.Abs(deltaX) > 50)
                {
                    if (!_isVertical)
                    {
                        if (_onceTimeIndicator)
                        {
                            _isHorizontal = true;
                            Canvas.SetLeft(ZoomArea, _mouseDragStartSpectrumPoint.X);
                            ZoomArea.Height = ChartCanvas.ActualHeight;
                            ZoomArea.Visibility = Visibility.Visible;
                            _onceTimeIndicator = false;
                        }
                        ZoomArea.Width = Math.Abs(deltaX);
                        if (deltaX > 0)
                        {
                            Canvas.SetLeft(ZoomArea, _mouseDragStartSpectrumPoint.X - deltaX);
                        }
                    }
                }

                if (Math.Abs(deltaY) > 50)
                {
                    if (!_isHorizontal)
                    {
                        if (_onceTimeIndicator)
                        {
                            _isVertical = true;
                            _onceTimeIndicator = false;
                            Canvas.SetTop(ZoomArea, _mouseDragStartSpectrumPoint.Y);
                            ZoomArea.Width = ChartCanvas.ActualWidth;
                            ZoomArea.Visibility = Visibility.Visible;
                        }
                        ZoomArea.Height = Math.Abs(deltaY);
                        if (deltaY > 0)
                        {
                            Canvas.SetTop(ZoomArea, _mouseDragStartSpectrumPoint.Y - deltaY);
                        }
                    }
                }
            }
            base.OnPreviewMouseMove(e);
        }

        private void ChartCanvasPreviewMouseLeftButtonUp1(object sender, MouseButtonEventArgs e)
        {
            ChartCanvas.ReleaseMouseCapture();

            var currentOffset = (ChartCanvasScroll.HorizontalOffset + Canvas.GetLeft(ZoomArea) - 50);
            var scaleX = ChartCanvasScroll.ActualWidth / ZoomArea.Width;
            if (_isHorizontal && scaleX < 100)
            {
                ScaleX = scaleX;
                ScrollTo(currentOffset * scaleX, 0);
            }
            var scaleY = ChartCanvasScroll.ActualHeight / ZoomArea.Height;
            if (_isVertical && scaleY < 100)
            {
                ScaleY = scaleY;
                ScrollTo(0, (ChartCanvasScroll.VerticalOffset + Canvas.GetTop(ZoomArea) - 50) * scaleY);
            }
            

            ZoomArea.Visibility = Visibility.Collapsed;
            ZoomArea.Width = 0;
            Canvas.SetTop(ZoomArea, 0);
            Canvas.SetLeft(ZoomArea, 0);

            _isVertical = false;
            _isHorizontal = false;
            _onceTimeIndicator = true;

            base.OnPreviewMouseLeftButtonUp(e);
        }

        private void ZoomOut(object sender, RoutedEventArgs e)
        {
            ScaleX = 1;
            ScaleY = 1;
        }
        #endregion

        #region OpenGl
        /*
        private bool one = true;
        private void openGLControl1_OpenGLDraw(object sender, SharpGL.SceneGraph.OpenGLEventArgs args)
        {
            // NOTE: Only draw once after m_drawCount is set to zero
            if (!one)
                return;

            one = false;
            //  Get the OpenGL instance.
            var gl = args.OpenGL;

            // ADDED THIS LINE
            gl.Clear(SharpGL.OpenGL.GL_DEPTH_BUFFER_BIT);

            gl.Color(1f, 0f, 0f);
            gl.PointSize(2.0f);
            gl.LineWidth(1.0f);

            //  Draw 10000 random points.
            gl.Begin(BeginMode.Lines);
            /*Random random = new Random();
            for (int i = 0; i < 10000; i++)
            {
                double x = 10 + 400 * random.NextDouble();
                double y = 10 + 400 * random.NextDouble();
                double z = (double)random.Next(-10, 0);
                /*
                                // Color the point according to z value
                                gl.Color(0f, 0f, 1f);  // default to blue
                                if (z == -9)
                                    gl.Color(1f, 0f, 0f);   // Red for z = -10
                                else if (z == -1)
                                    gl.Color(0f, 1f, 0f);   // Green for z = -1#2#

                //gl.Vertex(x, y, z);
                //gl.Vertex(x+10, y+10, z);
                Line_DDA(gl, x, y, x + 20, y + 20);
            }#1#
            if (ReferenceEquals(Source, null))
            {
                return;
            }

            var availableHeight = ChartCanvas.ActualHeight;
            var step = (ChartCanvas.ActualWidth - YAxis.ActualWidth) / Source.Count;
            var maxYValue = Source.ToList().MaxBy(x => x.SpectrumPoint.Y);
            var yScale = (availableHeight - MarginTop) / (maxYValue.SpectrumPoint.Y);
            var x2 = step;
            /*var myImage = new Image();
            myImage.SetBinding(Image.VisibilityProperty,
                               new Binding("IsChecked")
                                   {
                                       Source = visibilitySource,
                                       Converter = new BooleanToVisibilityConverter()
                                   });#1#
            var spectrumPoints = new List<SpectrumPoint>();
            for (int i = 1; i < Source.Count; i++)
            {
                gl.Vertex(x2, (Source[i - 1].SpectrumPoint.Y * yScale));
                gl.Vertex(x2 + step, (Source[i].SpectrumPoint.Y * yScale));
                //Line_DDA(gl, x2, (Source[i - 1].SpectrumPoint.Y * yScale), x2 + step, (Source[i].SpectrumPoint.Y * yScale));
                /*ChartCanvas.AddVirtualChild(new SimpleVirtualLine(
                                                x2, x2 + step, (double) (source[i - 1].SpectrumPoint.Y*yScale),
                                                (double) (source[i].SpectrumPoint.Y*yScale), brush));#1#
                x2 += step;
            }

            gl.End();
        }

        void Line_DDA(OpenGL gl, double X0, double Y0, double Xend, double Yend)
        {
            double dx = Xend - X0;
            double dy = Yend - Y0;
            double steps, k;
            float Xinc, Yinc;
            double x = X0;
            double y = Y0;

            if (Math.Abs(dx) > Math.Abs(dy))
                steps = Math.Abs(dx);
            else
                steps = Math.Abs(dy);

            float fdx = (float)dx;
            float fdy = (float)dy;
            float fsteps = (float)steps;
            Xinc = fdx / fsteps;
            Yinc = fdy / fsteps;

            gl.Vertex((int)x, (int)y);

            for (k = 0; k < steps; k++)
            {
                x += Xinc;
                y += Yinc;
                gl.Vertex((int)x, (int)y);
            }
        }

        private void openGLControl1_OpenGLInitialized(object sender, SharpGL.SceneGraph.OpenGLEventArgs args)
        {

        }

        private void openGLControl1_Resized(object sender, SharpGL.SceneGraph.OpenGLEventArgs args)
        {
            //  Get the OpenGL instance.
            var gl = args.OpenGL;

            //  Create an orthographic projection.
            gl.MatrixMode(MatrixMode.Projection);
            gl.LoadIdentity();
            //gl.Ortho(0, openGLControl1.ActualWidth, openGLControl1.ActualHeight, 0, -10, 10);

            //  Back to the modelview.
            gl.MatrixMode(MatrixMode.Modelview);

            one = true;
        }*/
        #endregion

        private void ChartScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            Canvas.SetTop(_directGraphDirect2DControl, ChartCanvasScroll.VerticalOffset);
            Canvas.SetLeft(_directGraphDirect2DControl, ChartCanvasScroll.HorizontalOffset);

            Canvas.SetTop(_directPeaks2DControl, ChartCanvasScroll.VerticalOffset);
            Canvas.SetLeft(_directPeaks2DControl, ChartCanvasScroll.HorizontalOffset);

            this._scrollTimer.Start();
        }

        private void ResizeTimerTick(object sender, EventArgs e)
        {
            this._scrollTimer.Stop(); // Only call this method once
            if (_directGraphDirect2DControl== null || _directGraphDirect2DControl.Scene == null)
            {
                return;
            }
            var baseViewpoints = CalculatePoints(Source);
            (_directGraphDirect2DControl.Scene as Graph2DScene).Source = new Dictionary<Color, IList<SpectrumDataPointViewModel>>
                                                                  {
                                                                      {
                                                                          Color.FromRgb(28, 151, 234),
                                                                          baseViewpoints
                                                                      },
                                                                      {
                                                                          Color.FromRgb(0, 159, 60),
                                                                          new List<SpectrumDataPointViewModel>(CalculatePoints(WaveletSource))
                                                                      }
                                                                  };
            (_directGraphDirect2DControl.Scene as Graph2DScene).Render();

            _peaks2DScene.Peaks = CalculatePeakPoints(Peaks.ToList(), baseViewpoints);
            _peaks2DScene.Render();
        }

        public void ResetZoom()
        {
            ScaleX = 1;
            ScaleY = 1;
        }
    }
}
